/*
 * @Author       : Xu Xiaokang
 * @Email        :
 * @Date         : 2025-05-07 11:29:51
 * @LastEditors  : Xu Xiaokang
 * @LastEditTime : 2025-05-18 12:16:07
 * @Filename     :
 * @Description  :
*/

/*
! 模块功能: 实测TDPRAM功能，测试同步读-写冲突
* 思路:
* 1.
~ 注意:
~ 1.
% 其它
*/

`default_nettype none

module MP5620_K7_myTDPRAM_Top_test_RW
(
  output wire [3:0] leds,

  input wire fpga_clk_p, //* 200MHz
  input wire fpga_clk_n
);


//++ 时钟和复位 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire clk_out1;
wire locked;
localparam CLK_FREQ_MHZ = 400;
clk_wiz_0  clk_wiz_0_u0 (
  .clk_in1_p (fpga_clk_p),
  .clk_in1_n (fpga_clk_n),
  .locked    (locked    ),
  .clk_out1  (clk_out1  )
);

wire clka = clk_out1;
wire clkb = clk_out1;

localparam RSTN_CLK_WIDTH = 3;
reg [RSTN_CLK_WIDTH + 1 : 0] rstn_cnt;
always @(posedge clka) begin // 使用最慢的时钟
  if (locked)
    if (~(&rstn_cnt))
      rstn_cnt <= rstn_cnt + 1'b1;
    else
      rstn_cnt <= rstn_cnt;
  else
    rstn_cnt <= 'd0;
end

/*
  初始为0, locked为高后经过2^RSTN_CLK_WIDTH个clk周期, rstn为1
  再过2^RSTN_CLK_WIDTH个clk周期, rstn为0
  在过2^RSTN_CLK_WIDTH个clk周期后, rstn为1, 后续会保持1
  总的来说, 复位低电平有效持续(2^RSTN_CLK_WIDTH)个clk周期
*/
wire probe_out0;
wire rstn = rstn_cnt[RSTN_CLK_WIDTH] && probe_out0;
//-- 时钟和复位 ------------------------------------------------------------


//++ VIO ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// wire probe_out0;
wire [15:0 ] probe_out1;
vio_0 vio_0_u0 (
  .clk        (clka      ),
  .probe_out0 (probe_out0),
  .probe_out1 (probe_out1)
);
//-- VIO ------------------------------------------------------------


//++ LED ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam LED_CLK_CNT_MAX = CLK_FREQ_MHZ * 1000 * 1000;
reg [$clog2(LED_CLK_CNT_MAX+1)-1 : 0] led_clk_cnt;
always @(posedge clka) begin
  if (~rstn)
    led_clk_cnt <= 'd0;
  else if (led_clk_cnt < LED_CLK_CNT_MAX)
    led_clk_cnt <= led_clk_cnt + 1'b1;
  else
    led_clk_cnt <= 'd0;
end

assign leds[0] = led_clk_cnt < LED_CLK_CNT_MAX / 2;
assign leds[1] = led_clk_cnt < LED_CLK_CNT_MAX / 2;
assign leds[2] = led_clk_cnt > LED_CLK_CNT_MAX / 2;
assign leds[3] = led_clk_cnt > LED_CLK_CNT_MAX / 2;
//-- LED ------------------------------------------------------------


//++ 测试模块实例化 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam RAM_STYLE = "block"; //* RAM类型, 可选"block"(默认), "distributed"
localparam DATA_WIDTH = 10; //* 数据位宽, 可选1, 2, 3, ..., 默认为8
localparam ADDR_WIDTH = 4; //* RAM地址位宽, 对应RAM深度, 可选1, 2, 3, ..., 默认为6, 对应深度2**6=64
localparam OPERATING_MODE_A = "RF"; //* 可选"Write First"(默认), "Read First", "No Change"
localparam OPERATING_MODE_B = "RF"; //* 可选"Write First"(默认), "Read First", "No Change"
localparam [0:0] USE_ENA = 0; //* 启用ENA信号
localparam [0:0] USE_ENB = 0; //* 启用ENB信号
localparam [1:0] OUTPUT_REG_NUM = 0; //* 可选0(默认), 1, 2
//* 初始化文件名，空(默认)表示不初始化，示例目录C:\_myJGY\ram_init.coe
// localparam INIT_FILE = "C:/_myJGY/17_Markdown/_myOpenSource/verilog-function-module--RAM_ROM/PRJ/ram_rom_init_my.coe";
localparam INIT_FILE = "";
/*
* 默认初始值, 在未指定初始化文件或初始化文件行数比RAM深度小时起作用, 使用16进制表示, 默认值0,
* 对应Vivado BRAM IP的功能Fill Remaining Memory Locations
*/
parameter [DATA_WIDTH-1:0] INIT_VALUE_HEX = 'h245;

// (* mark_debug *)reg ena;
(* mark_debug *)reg wea;
(* mark_debug *)reg [ADDR_WIDTH-1 : 0] addra;
(* mark_debug *)reg [DATA_WIDTH-1 : 0] dina;
(* mark_debug *)wire [DATA_WIDTH-1 : 0] douta;
(* mark_debug *)wire [DATA_WIDTH-1 : 0] vivado_douta;

// (* mark_debug *)reg enb;
(* mark_debug *)reg web;
(* mark_debug *)reg [ADDR_WIDTH-1 : 0] addrb;
(* mark_debug *)reg [DATA_WIDTH-1 : 0] dinb;
(* mark_debug *)wire [DATA_WIDTH-1 : 0] doutb;
(* mark_debug *)wire [DATA_WIDTH-1 : 0] vivado_doutb;

myTDPRAM_Top_collision #(
  .RAM_STYLE        (RAM_STYLE       ),
  .DATA_WIDTH       (DATA_WIDTH      ),
  .ADDR_WIDTH       (ADDR_WIDTH      ),
  .OPERATING_MODE_A (OPERATING_MODE_A),
  .OPERATING_MODE_B (OPERATING_MODE_B),
  .USE_ENA          (USE_ENA         ),
  .USE_ENB          (USE_ENB         ),
  .OUTPUT_REG_NUM   (OUTPUT_REG_NUM  ),
  .INIT_FILE        (INIT_FILE       ),
  .INIT_VALUE_HEX   (INIT_VALUE_HEX  )
) myTDPRAM_Top_collision_inst (
  .clka         (clka        ),
  .ena          (            ),
  .wea          (wea         ),
  .addra        (addra       ),
  .dina         (dina        ),
  .douta        (douta       ),
  .vivado_douta (vivado_douta),
  .clkb         (clkb        ),
  .enb          (            ),
  .web          (web         ),
  .addrb        (addrb       ),
  .dinb         (dinb        ),
  .doutb        (doutb       ),
  .vivado_doutb (vivado_doutb)
);
//-- 测试模块实例化 ------------------------------------------------------------


//++ 实例化随机数模块 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire [15:0] random_num;
wire [15:0] seed = probe_out1;
lfsr lfsr_inst (
  .random_num (random_num),
  .seed       (seed      ),
  .clk        (clka      ),
  .rstn       (rstn      )
);
//-- 实例化随机数模块 ------------------------------------------------------------


//++ 产生数据和地址 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
always @(posedge clka) begin
  wea   <= random_num[13];
  addra <= random_num[ADDR_WIDTH-1+8 : 8];
  dina  <= random_num[DATA_WIDTH-1+2 : 2];
end

reg web_tmp;
always @(posedge clkb) begin
  web_tmp <= random_num[5];
  addrb   <= random_num[ADDR_WIDTH-1 : 0];
  dinb    <= random_num[DATA_WIDTH-1 : 0];
end

always @(*) begin
  if (addra == addrb && wea)
    web = 1'b0;
  else
    web = web_tmp;
end

(* mark_debug *)reg douta_is_not_equal_vivado_douta;
(* mark_debug *)reg doutb_is_not_equal_vivado_doutb;
always @(posedge clka) begin
  douta_is_not_equal_vivado_douta <= douta != vivado_douta;
  doutb_is_not_equal_vivado_doutb <= doutb != vivado_doutb;
end
//-- 产生数据和地址 ------------------------------------------------------------


//++ 冲突检测 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(* mark_debug *)wire collision_detected; //* 发生冲突则置高
(* mark_debug *)reg write_collision_clka = 0; //* A与B同时写冲突
(* mark_debug *)reg write_collision_clkb = 0; //* A与B同时写冲突
(* mark_debug *)reg a_wr_b_rd_collision = 0; //* A写B读冲突
(* mark_debug *)reg a_rd_b_wr_collision = 0; //* A读B写冲突

always @(posedge clka) begin
  if (addra == addrb && wea && web) write_collision_clka <= 1'b1;
  else write_collision_clka <= 1'b0;
end

always @(posedge clkb) begin
  if (addra == addrb && wea && web) write_collision_clkb <= 1'b1;
  else write_collision_clkb <= 1'b0;
end

// A 写且 B 读同一地址
always @(posedge clka) begin
  if (addra == addrb && wea && ~web)
    a_wr_b_rd_collision <= 1'b1;
  else
    a_wr_b_rd_collision <= 1'b0;
end

// A 读且 B 写同一地址
always @(posedge clkb) begin
  if (addra == addrb && ~wea && web)
    a_rd_b_wr_collision <= 1'b1;
  else
    a_rd_b_wr_collision <= 1'b0;
end

assign collision_detected = write_collision_clka || write_collision_clkb
                            || a_wr_b_rd_collision || a_rd_b_wr_collision;
//-- 冲突检测 ------------------------------------------------------------


endmodule
`resetall